home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Internet / WWW / Perl_WWW_Utilities / page-stats / page-stats.pl < prev    next >
Encoding:
Perl Script  |  1995-07-25  |  14.2 KB  |  493 lines

  1. #!/usr/local/gnu/bin/perl
  2. #
  3. # "page-stats v1.3": a Perl-program by Mark Koenen <markko@sci.kun.nl>
  4. # that 'cleverly' checks how often a WWW-page has been accessed.
  5. # Changes by Patrick Atoon <patricka@cs.kun.nl>.
  6. #
  7. # Usage: page-stats.pl -h
  8. #        page-stats.pl [ -b ] [ -i identfile ] [ -l logfile ]
  9. #
  10. # Where: 
  11. # The pages that have to be counted are defined in the 'identfile'.
  12. # 'logfile' gives the exact location of the log-file. 
  13. #
  14. # For details on the ident-file, do "page-stats.pl -h" to view the
  15. # manual page.
  16. #
  17. # Latest version on : http://www.sci.kun.nl/thalia/guide/
  18. # A working example : http://www.sci.kun.nl/thalia/page-stats/
  19. #
  20.  
  21. # Just some defaults:
  22. $logfile = "/vol/www/ezel/httpd/logs/access_log";
  23. $identfile = "page-stats_en.ident";
  24. $total_number_req = 0;
  25. $benchmark = 0;
  26.  
  27. # Process switches:
  28. while ($ARGV[0] =~ /^-/)
  29. {
  30.     $_ = shift;
  31.     if (/^-i/) 
  32.     {
  33.         $identfile = shift;
  34.     }
  35.     elsif (/^-l/) 
  36.     {
  37.         $logfile = shift;
  38.     }
  39.     elsif (/^-h/)
  40.     {
  41.         &print_help;
  42.         exit(0);
  43.     }
  44.     elsif (/^-b/)
  45.     {
  46.         $benchmark = 1;
  47.     }
  48.     else 
  49.     {
  50.         die "Unrecognized switch: $_.\n"; 
  51.     }
  52. }
  53.  
  54. if (! -r $logfile)
  55. {
  56.     die "Cannot open logfile $logfile.\n";
  57. }
  58.  
  59. if (! -r $identfile)
  60. {
  61.     die "Cannot open identfile $identfile.\n";
  62. }
  63.  
  64. # Are we benchmarking?
  65. ($u, $s) = times if ($benchmark);
  66.  
  67. # Well, the important files are there. Start generating the
  68. # HTML-file
  69.  
  70. # Let's determine the other filenames:
  71. $strippedfile = substr($identfile, 0, rindex($identfile, "."));
  72. $htmlfile = $strippedfile . ".html";
  73. $sourcefile = $strippedfile . ".source";
  74.  
  75. @test_url = ();        # (Partial) URLs to match
  76. @true_idx = ();        # Index of true URL to which (partial) URLs belong
  77. @true_url = ();        # True URLs
  78. @url_desc = ();        # Description of the true URL
  79. @urlcount = ();        # Number of hits for those true URLs
  80.  
  81. # Read the identifiers-file:
  82. open(IDENT, $identfile);
  83.  
  84. while (<IDENT>)
  85. {
  86.     chop;
  87.  
  88.     s/#.*//g;     # Throw out comments
  89.     s/\s$//g;     # Throw out spaces at the end
  90.  
  91.     next if ($_ eq "");
  92.  
  93.     @info = split("@", $_);
  94.  
  95.     # Fill in the various arrays that administrate stuff.
  96.     $index = scalar(@true_url);
  97.     push(@true_url, shift(@info));
  98.     push(@url_desc, shift(@info));
  99.     push(@urlcount, 0);
  100.  
  101.     foreach $url (@info)
  102.     {
  103.     push(@true_idx, $index);
  104.     push(@test_url, $url);
  105.     }
  106. }
  107.  
  108. close(IDENT);
  109. $num_true_urls = scalar(@true_url);
  110. $num_test_urls = scalar(@test_url);
  111. open(LOG, $logfile);
  112.  
  113. #
  114. # Read the logfile and check if the page is recognized.
  115. #
  116. # This is computationally the heaviest part of the script.
  117. # Optimizing would help a lot.
  118. #
  119. while (<LOG>)
  120. {
  121.     # Since HMTL pages almost always contain pictures, it is more
  122.     # cost-effective to filter these pictures out before trying to
  123.     # match these lines. Even if each page only contains one
  124.     # picture, this would already save 50% of the lines to match!
  125.     # In real life pages, this percentage will be much higher.
  126.     #
  127.     # If you want to be able to match pictures, outcomment the next
  128.     # two lines by placing a "#" before them.
  129.     next if (index($_, ".gif ") != -1);
  130.     next if (index($_, ".jpg ") != -1);
  131.  
  132.     # Get URL from logline; they happen to start with " /", how handy!
  133.     # Use index(), it's faster than using s/X/Y/.
  134.     $begin = index($_, " /") + 1;
  135.  
  136.     next if ($begin == -1);
  137.  
  138.     # The next space marks the end of the URL.
  139.     $end = index($_, " ", $begin);
  140.     $pageurl = substr($_, $begin, $end-$begin);
  141.  
  142.     for ($i = 0; $i < $num_test_urls; $i++)
  143.     {
  144.         # Is this one with or without the wildcard?
  145.         if (substr($test_url[$i], -1) eq "*")
  146.         {
  147.         $url = $test_url[$i];
  148.             chop($url);
  149.             $len = length($url);
  150.  
  151.         # If this matches the pageurl, increase the counter.
  152.         if (substr($pageurl, 0, $len) eq $url)
  153.         {
  154.                 $urlcount[$true_idx[$i]]++;
  155.                 $total_number_req++;
  156.         last;        # No need to check others; continue with next URL.
  157.         }
  158.         }
  159.         else
  160.         {
  161.         # If this is the pageurl, increase the counter.
  162.         if ($pageurl eq $test_url[$i])
  163.         {
  164.                 $urlcount[$true_idx[$i]]++;
  165.                 $total_number_req++;
  166.         last;        # No need to check others; continue with next URL.
  167.         }
  168.         }
  169.     }
  170. }
  171.  
  172. close (LOG);
  173.  
  174. # Calculate some variables:
  175. $firstrequest = `head -1 $logfile`;
  176. $firstrequest =~ s/^.*\[(\S*)\s.*$/$1/;
  177.  
  178. $lastrequest = `tail -1 $logfile`;
  179. $lastrequest =~ s/^.*\[(\S*)\s.*$/$1/;
  180.  
  181. chop($firstrequest, $lastrequest);
  182.  
  183. # Determine the time of creation
  184. ($sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst) = localtime();
  185. @MONTHS = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
  186.            "Oct", "Nov", "Dec");
  187. $date = sprintf("%02d/%s/19%02d:%02d:%02d:%02d", $day, $MONTHS[$month], $year,
  188.                 $hour, $min, $sec);
  189.  
  190. # Open HTML-file for writing
  191. if (!open(HTML, ">$htmlfile"))
  192. {
  193.     die "Cannot open html file $htmlfile for writing.\n";
  194. }
  195.  
  196. # Write SOURCE-file to HTML-file
  197. if (open(SOURCE, $sourcefile))
  198. {
  199.     while (<SOURCE>)
  200.     {
  201.         # Replace requested variables
  202.         s/\$firstrequest/$firstrequest/gi;
  203.         s/\$lastrequest/$lastrequest/gi;
  204.         s/\$date/$date/gi;
  205.  
  206.         $line = $_;
  207.  
  208.         # Insert top number pages
  209.         if (/\$top/i)
  210.         {
  211.             # Get the limit
  212.             $limit = $line;
  213.             $limit =~ s/.*\$top([0-9]*).*$/$1/i;
  214.  
  215.             # Don't forget to erase the $topXX bit from the line.
  216.             $line =~ s/\$top[0-9]*//gi;
  217.  
  218.             &write_top($limit);
  219.  
  220.         }
  221.  
  222.         # Insert all pages
  223.         if ($line =~ /\$list/)
  224.         {
  225.             $line =~ s/\$list//gi;
  226.             &write_pages;
  227.         }
  228.     print HTML $line;
  229.     }
  230. }
  231. else
  232. {
  233.     # Generate a default page
  234.     print HTML "<HTML>\n<HEAD>\n<TITLE>Page-statistics</TITLE>\n";
  235.     print HTML "</HEAD>\n<BODY>\n";
  236.     &write_pages;
  237.     print HTML "<HR>\n<EM>Page was generated on $date</EM>\n";
  238.     print HTML "</BODY>\n</HTML>\n";
  239. }
  240.  
  241. close(SOURCE);
  242. close(HTML);
  243.  
  244. if ($benchmark)
  245. {
  246.     ($nu, $ns) = times;
  247.     printf "%8.4f secs user time, %8.4f secs system time.\n", ($nu - $u),
  248.         ($ns - $s);
  249. }
  250.  
  251. exit(0);
  252.  
  253.  
  254. ###############################
  255. # Subroutines from here on
  256. #
  257.  
  258. #
  259. # Print all pages
  260. #
  261. sub write_pages
  262. {
  263.     local($i, $whitespace, $desc) = (0, "", "");
  264.  
  265.     # Write page-stats to HTML-file
  266.     print (HTML "<PRE>\n");
  267.  
  268.     for ($i = 0; $i < $num_true_urls; $i++)
  269.     {
  270.         $desc = $url_desc[$i];
  271.         $whitespace = $desc;
  272.         $desc =~ s/^\s*//;
  273.         $whitespace =~ s/^(\s*).*/$1/;
  274.     printf HTML ("%6d %s<A HREF=\"%s\">%s</A>\n", $urlcount[$i],
  275.         $whitespace, $true_url[$i], $desc);
  276.     }
  277.  
  278.     # print out the total number of requests
  279.     print HTML "--------------------------------\n";
  280.     printf HTML ("%6d  Total number of requests\n", $total_number_req);
  281.     print HTML "</PRE>\n";
  282. }
  283.  
  284. #
  285. # Print the top X
  286. #
  287. sub write_top
  288. {
  289.     local($lim) = @_;
  290.     local($i, $j, $max, $max_idx) = (0, 0, -1, -1);
  291.  
  292.     # Sanity check
  293.     $lim = 0 if ($lim < 0);
  294.     $lim = $num_true_urls if ($lim > $num_true_urls);
  295.  
  296.     @tmpcount = @urlcount;
  297.     print HTML "<PRE>\n";
  298.  
  299.     for ($i = 0; $i < $lim; $i++)
  300.     {
  301.         $max = -1;
  302.         $max_idx = -1;
  303.  
  304.         for ($j = 0; $j < $num_true_urls; $j++)
  305.         {
  306.             if ($tmpcount[$j] > $max)
  307.             {
  308.                 $max = $tmpcount[$j];
  309.                 $max_idx = $j;
  310.             }
  311.         }
  312.  
  313.     if ($max > -1)
  314.     {
  315.             $desc = $url_desc[$max_idx];
  316.         $desc =~ s/^\s*//;
  317.         printf HTML ("%6d <A HREF=\"%s\">%s</A>\n", $tmpcount[$max_idx],
  318.         $true_url[$max_idx], $desc);
  319.         $tmpcount[$max_idx] = -1;
  320.     }
  321.         else
  322.         {
  323.             last;
  324.         }
  325.     }
  326.  
  327.     print HTML "</PRE>\n";
  328. }
  329.  
  330. #
  331. # The manual is included in the program, so you can never lose it.
  332. #
  333. sub print_help
  334. {
  335.     print <<EOF;
  336.  
  337. NAME
  338.        page-stats.pl - Check WWW page accesses (v1.3)
  339.  
  340. SYNOPSIS
  341.        page-stats.pl -h
  342.        page-stats.pl [ -b ] [ -i identfile ] [ -l logfile ]
  343.  
  344. DESCRIPTION
  345.        page-stats.pl will examine the acceslog of a http daemon and search
  346.        it for occurrences of certain references. These references are then
  347.        counted  and  put into a HTML file that is ready to be displayed to
  348.        the  outside  world  as  a "Page Statistics" page. Each page can be
  349.        selected from the statistics page.
  350.  
  351.        The  identfile  contains  the  references that should be counted. A
  352.        line in this file should be in the following format:
  353.  
  354.               URL\@title\@reference[\@reference...]
  355.  
  356.        which could look like this:
  357.  
  358.               ~gnu/index.html\@Gnu's pages\@/gnu.html\@~gnu*
  359.  
  360.        Comments  are  allowed, and should be preceded by a "#". Everything
  361.        following that character will be ignored. Each line should at least
  362.        contain the following:
  363.  
  364.        URL    The  URL  of  the  page, as it should be referenced from the
  365.               "Page Statistics" page.
  366.  
  367.        title  The  title of the page, as you want visitors to see it. Note
  368.               that  leading  spaces  are significant, so it is possible to
  369.               make  use  of indentation for different levels of documents.
  370.  
  371.        reference
  372.               A reference of how the page might be accessed. For instance,
  373.               if  a  directory  contains  a  file  index.html,  it  can be
  374.               accessed  by  leaving out the "index.html" part, or even the
  375.               "/"  before  it.  If  this  is  the case, put all references
  376.               behind each other, separated by "\@". You may use a wildcard
  377.               "*"  at  the  end  of a string to match only the begin of an
  378.               URL.
  379.  
  380.        The  order  of  the  lines in the identfile matters. Only the first
  381.        match  will be taken into account. Be careful when using wildcards,
  382.        as  they  might filter out hits for lines below. Take a look at the
  383.        (faulty) example below:
  384.  
  385.               # Wrong; second line will never be reached!
  386.               ~gnu/index.html\@Gnu's pages\@~gnu*
  387.               ~gnu/info/index.html\@Gnu's info files\@~gnu/info*
  388.  
  389.        The  first  line will filter out all URLs ending in ".html", which
  390.        automatically  means  that  URLs that would match /info/*.html are
  391.        matched  as  well.  Place the second line above the first to solve
  392.        the problem:
  393.  
  394.               # Right!
  395.               ~gnu/info/index.html\@Gnu's info files\@~gnu/info*
  396.               ~gnu/index.html\@Gnu's pages\@~gnu*
  397.  
  398.        Currently  page-stats.pl  will  skip  lines  in the access_log that
  399.        contain  references to ".gif", ".jpg" or ".jpeg" files, even if you
  400.        specify  matching  URLs.  If  you  need  the  program to be able to
  401.        handle  references  to  those  pictures,  you should outcomment the
  402.        lines as indicated in the code.
  403.  
  404.        Note  that  once  the  first matching reference is found, the quest
  405.        for  matches  is ended. Only the first page will be recognized as a
  406.        matching reference and its counter will be increased.
  407.  
  408.        The  HTML  "Page Statistics"  file is created from two files. These
  409.        are the ident file with references to check, and a source file that
  410.        contains  the  basic  HTML  page as desired. The name of the source
  411.        file  is  determined  by replacing the mandatory ".ident" ending of
  412.        the  ident file by ".source". The HTML file that is created will be
  413.        named in the same way, ending in ".html".
  414.  
  415.        It  is  possible to use certain variables in the source file. These
  416.        variables  will be replaced by page-stats.pl as it rummages through
  417.        the file.
  418.  
  419.        \$date  The  current  date  and  time  will  be  inserted  for  this
  420.               variable.
  421.  
  422.        \$firstrequest
  423.               The  date  and  time  of  the  first  request  logged in the
  424.               access_log will be inserted for this variable.
  425.  
  426.        \$lastrequest
  427.               This  variable is replaced by the last request logged in the
  428.               access_log.
  429.  
  430.        \$list  This  will  be  replaced  by the complete list of references
  431.               and their number of hits.
  432.  
  433.        \$topN  This  will insert a sorted list of the N most visited pages,
  434.               where  N  can  be  any  number .  Of course setting a number
  435.               greater  than  the number of references is silly. There must
  436.               be no space between "\$top" and the number.
  437.  
  438. OPTIONS
  439.        -b     Benchmark;  print  used  user  and  system times when ready.
  440.  
  441.        -h     Displays this manual page.
  442.  
  443.        -i identfile
  444.               Specify  the  file  that determines which references to look
  445.               for  in  the  logfile.  This defaults to 'page-stats.ident'.
  446.  
  447.        -l logfile
  448.               Specify  the  access_log  of  the  http  daemon. The default
  449.               location is '/usr/local/httpd/logs/access_log'.
  450.  
  451. FILES
  452.        access_log           (generated by httpd)
  453.        <identname>.ident
  454.        <identname>.source   (optional)
  455.        <identname>.html     (generated by page-stats.pl)
  456.  
  457. SEE ALSO
  458.        httpd(1).
  459.        http://www.sci.kun.nl/thalia/guide/#page-stats
  460.               For the latest version.
  461.        http://www.sci.kun.nl/thalia/page-stats/
  462.               For a working example.
  463.  
  464. CHANGES
  465.        03-01-1995:  (v1.0) First draft of the program.
  466.        03-17-1995:  (v1.1) Added 'total number of requests' at the bottom
  467.                     of the page.
  468.        05-26-1995:  (v1.2) Added  '\$topN'  and  '\$list'; juggled with the
  469.                     code.  Improved  performance  by  skipping  images in
  470.                     access_log.  Allowed comments in the ident file. Also
  471.                     moved the external README into the code.
  472.        07-17-1995:  (v1.3) You  can  now  use wildcards to define URLs to
  473.                     recognize.  Using arrays to administrate URLs instead
  474.                     of strings.
  475.        
  476. BUGS
  477.        If  the  accesslog is big, and there are many references to check,
  478.        this  program  can  take  very long to complete. It is recommended
  479.        that  both  the size of the accesslog and the number of references
  480.        are kept to acceptable levels.
  481.  
  482.        The  program  might not work because the path to Perl in the first
  483.        line  of  page-stats.pl  is  wrong.  See if the path is correct by
  484.        doing  'which perl' at your Unix prompt. If it is not correct, you
  485.        will have to edit the first line.
  486.  
  487. AUTHOR
  488.        Mark Koenen <markko\@sci.kun.nl>,
  489.        changes by Patrick Atoon <patricka\@cs.kun.nl>
  490.  
  491. EOF
  492. }
  493.